#include "degfx/driver.h"

#ifdef MX__DRIVER_SVGALIB

#   define MXMODULE_DRS

#   include <assert.h>
#   include <vga.h>
#   include <vgamouse.h>
#   include <vgakeyboard.h>

static void mx__linux_svgalib_palette(void)
{
	unsigned i;

	for (i = 0; i <= 255; i++) {
		unsigned char r = (i & 0xe0) + (i >> 3);
		unsigned char g = ((i & 0x1c) << 3) + (i >> 3);
		unsigned char b = ((i & 0x07) << 6) + (i >> 2);

		vga_setpalette(i, r, g, b);
	}
}

static unsigned mx__linux_svgalib_started = false;

#   define MX__SQR(a) ((a)*(a))

static unsigned mx__linux_svgalib_start(MX_GFX_ARGS * args)
{
	int mode, bestmode, bestscore;

	if (!args->w)
		args->w = 640;
	if (!args->h)
		args->h = 480;
	if (!args->c)
		args->c = 16;

	if (!mx__linux_svgalib_started)
		vga_init();
	mx__linux_svgalib_started = true;

	bestmode = vga_getdefaultmode();
	if (bestmode == -1)
		bestmode = G320x200x256;
	bestscore = -1;

	/* Walk down the list of modes */
	for (mode = 1; mode < vga_lastmodenumber(); mode++) {
		const vga_modeinfo *modeinfo = 0;

		if (!vga_hasmode(mode))
			modeinfo = vga_getmodeinfo(mode);

		if (modeinfo) {
			const int x = modeinfo->width;
			const int y = modeinfo->height;
			const int c = modeinfo->bytesperpixel * 8;
			const int score = MX__SQR(x - args->w)
				+ MX__SQR(y - args->h) + MX__SQR(c - args->c);

			if ((score < bestscore) || (bestscore < 0)) {
				bestmode = mode;
				bestscore = score;
			}
		}
	}

	vga_setmode(bestmode);
	vga_setmousesupport(1);
	mx__linux_svgalib_palette();

	/* Inform the user of the results */
	args->w = vga_getxdim();
	args->h = vga_getydim();
	args->c = vga_getmodeinfo(vga_getcurrentmode())->bytesperpixel * 8;
	args->title = "linux_svgalib";
	args->pointer = false;

	mx_drs_area(args->w - 1, args->h - 1);

	return true;
}

static void mx__linux_svgalib_stop(void)
{
	vga_setmousesupport(0);
	vga_setmode(TEXT);
}

static void mx__linux_svgalib__flush(const MX_RECT * rect, MX_PIXEL * array, const int stride)
{
	int y;

	/* Push the buffer to the visible screen */
	y = rect->y1;
	while (y <= rect->y2) {
		MX_PIXEL *src = array;
		int count = (rect->x2 - rect->x1);
		int x = rect->x1;

		/* Convert pixel-by-pixel :( */
		while (count--) {
			vga_setrgbcolor(MXR(*src), MXG(*src), MXB(*src));
			vga_drawpixel(x, y);
			++src;
			++x;
		}

		/* Next row to flush */
		array += stride;
		++y;
	}
}

static void mx__linux_svgalib_flush(MX_RECT * rect)
{
	/*      const int w = mx_w(mx__args.buffer);

	   Make the area smaller than the buffer and align the X value to 8 pixels
	   so that the flush function can be simpler.
	   rect->x1 -= (rect->x1 % 8);
	   rect->x2 += 7 - (rect->x2 % 8);

	   if ((rect->x2 - rect->x1) > w) {
	   rect->x2 = rect->x1 + w;
	   rect->x2 -= ((rect->x2 + 1) % 8);
	   }
	 */
	mx__gfx_flush(mx__linux_svgalib__flush, rect);
}

static unsigned mx__linux_svgalib_poll(void)
{
	mx_drs_update(mx__linux_svgalib_flush);
	return true;
}

static void mx__linux_svgalib_dirty(const MX_RECT * rect)
{
	mx_drs_dirty(rect, true);
}

static unsigned mx__linux_svgalib_mouse(void)
{
	int button;

	/* Poll the mouse */
	mouse_update();
	mx__mousex = mouse_getx();
	mx__mousey = mouse_gety();
	button = mouse_getbutton();

	mx__mouseb = 0;
	if (button & MOUSE_LEFTBUTTON)
		mx__mouseb |= 0x01;

	/* Limit the mouse coordinates to on screen */
	mx__mousex = MX_MID(mx__args.screen.x1, mx__mousex, mx__args.screen.x2);
	mx__mousey = MX_MID(mx__args.screen.y1, mx__mousey, mx__args.screen.y2);
	return true;
}

static unsigned mx__linux_svgalib_key(int *scan, int *ascii)
{
	int lastkey;

	/* Poll the keyboard */
	keyboard_update();

	lastkey = vga_getkey();
	if (!lastkey)
		return false;

	*scan = 0;
	*ascii = lastkey;
	return true;
}

const MX_DRIVER mx__svgalib = {
	mx__linux_svgalib_start,
	mx__linux_svgalib_stop,

	mx__linux_svgalib_poll,
	mx__linux_svgalib_dirty,

	mx__linux_svgalib_mouse,
	mx__linux_svgalib_key
};

#endif
